// Copyright (C) ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
//
// $Id: xpath.cpp 209 2008-08-28 10:05:39Z heavyzheng $
//

#ifndef ZZHENG_XML_SOURCE
#define ZZHENG_XML_SOURCE
#endif // ZZHENG_XML_SOURCE

#include <zzheng/xml/dom/libxml2/xpath.hpp>
#include <zzheng/xml/dom/libxml2/xpath_result.hpp>
#include <zzheng/xml/dom/libxml2/node.hpp>
#include <zzheng/xml/dom/libxml2/document.hpp>
#include <zzheng/xml/dom/libxml2/utility.hpp>

#include <zzheng/xml/dom/exception.hpp>

#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <boost/noncopyable.hpp>
#include <cassert>
#include <string>

namespace zzheng {
namespace xml {
namespace dom {
namespace libxml2_ {

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // anonymous namespace
    //

    namespace {

        //! A wrapper around xmlXPathContext, using the RAII idiom.
        class xpath_context_wrapper {

        public:

            explicit xpath_context_wrapper(xmlXPathContext* px): raw_(px) {
                // Do nothing.
            }

            ~xpath_context_wrapper() {
                if (raw_ != 0) {
                    xmlXPathFreeContext(raw_);
                    raw_ = 0;
                }
            }

            xmlXPathContext* get() {
                return raw_;
            }

        private:

            //! Non-implemented copy constructor.
            xpath_context_wrapper(const xpath_context_wrapper&);

            //! Non-implemented copy assignment.
            xpath_context_wrapper& operator=(const xpath_context_wrapper&);

        private:

            xmlXPathContext* raw_; //!< Raw pointer to the underlying xmlXPathContext object.

        }; // class xpath_context_wrapper

        //! Libxml2 callback function for XPath errors.
        //! \todo TODO: a better error reporting mechanism?
        void libxml2_on_xpath_error(void*, xmlError*) {
            // Do nothing: just to prevent libxml2 from writing error message to console.
        }

    } // anonymous namespace

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // xpath implementation
    //

    void xpath::add_namespace(const std::string& uri, const std::string& prefix) {
        namespaces_[prefix] = uri;
    }

    xpath_result xpath::evaluate(const std::string& expr, node& nd) const {
        xpath_result result;
        evaluate_(expr, nd.doc().raw(), nd.raw(), result);
        return result;
    }

    xpath_result xpath::evaluate(const std::string& expr, document& doc) const {
        xpath_result result;
        evaluate_(expr, doc.raw(), 0, result);
        return result;
    }

    void xpath::evaluate(const std::string& expr, node& nd, xpath_result& result) const {
        evaluate_(expr, nd.doc().raw(), nd.raw(), result);
    }

    void xpath::evaluate(const std::string& expr, document& doc, xpath_result& result) const {
        evaluate_(expr, doc.raw(), 0, result);
    }

    void xpath::evaluate_(const std::string& expr,
                          xmlDoc* px_doc,
                          xmlNode* px_node,
                          xpath_result& result) const {

        // Create libxml2 xpath context on the xml document.
        xpath_context_wrapper context(xmlXPathNewContext(px_doc));
        if (context.get() == 0) {
            std::string errmsg = "Fail to evaluate XPath expression '" + expr + "': "
                               + "xmlXPathNewContext returns null xmlXPathContext.";
            throw xpath_error(errmsg.c_str());
        }

        // Register the callback error function.
        context.get()->error = &libxml2_on_xpath_error;

        // Register namespace prefix-uri mappings to the xpath context.
        typedef namespace_map::const_iterator const_iterator;
        for (const_iterator i = namespaces_.begin(); i != namespaces_.end(); ++i) {
            int zero = xmlXPathRegisterNs( context.get(),                     // xpath context
                                           to_xml_chars(i->first.c_str()),    // ns prefix
                                           to_xml_chars(i->second.c_str()) ); // ns uri
            (zero == 0);
        }

        // Set the starting point of the xpath evaluation.
        context.get()->node = px_node;

        // Evaluate the xpath expression and return the xpath data object.
        xmlXPathObject* px_result = xmlXPathEvalExpression( to_xml_chars(expr.c_str()),
                                                            context.get() );
        if (px_result == 0) {
            std::string errmsg = "Fail to evaluate XPath expression '" + expr + "': "
                               + build_error_message(context.get()->lastError);
            throw xpath_error(errmsg.c_str());
        } else {
            result.set(px_result);
        }
    }

} // namespace zzheng::xml::dom::libxml2_
} // namespace zzheng::xml::dom
} // namespace zzheng::xml
} // namespace zzheng





